package com.harry.configuration.web.interceptor.apiproxy;

import java.beans.PropertyDescriptor;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.aop.Advisor;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.harry.basis.BasePoEntity;
import com.harry.configuration.ConstSettings;
import com.harry.configuration.poentity.PoVali;
import com.harry.decoupling.defi.proxy.RestfulApiProxy;
import com.harry.utility.InnerUtils;
import com.harry.utility.PoEntityHelper;

@Configuration
public class ValidationInterceptor {

	@Bean
	public Advisor validation() throws Exception {
		return InnerUtils.cutInterface(invocation -> {
			String action = invocation.getMethod().getName();
			if ("post".equals(action) || "patch".equals(action)) {
				@SuppressWarnings("unchecked")
				Map<String, Object> params = Map.class.cast(invocation.getArguments()[1]);
				String resourceName = InnerUtils.findArgs(invocation.getArguments(), String.class);
				Class<? extends BasePoEntity> clazz = ConstSettings.LRsrcEntityMap.get(resourceName);
				validate(clazz, params);
			}
		}, RestfulApiProxy.class, ConstSettings.FIRST_ORDER + 1);
	}

	private void validate(Class<? extends BasePoEntity> clazz, Map<String, Object> params) throws Exception {
		validateObject(clazz, null, params, new HashSet<Integer>());
	}

	private String[] getFieldNamesForValidate(Class<? extends BasePoEntity> clazz, Map<String, Object> params) {
		Set<String> ignSet = Stream.of(BeanUtils.getPropertyDescriptors(BasePoEntity.class)).map(pd -> pd.getName()).collect(Collectors.toSet());
		Set<String> allSet = Stream.of(BeanUtils.getPropertyDescriptors(clazz)).map(pd -> pd.getName()).collect(Collectors.toSet());
		Set<String> usefulSet = SetUtils.difference(allSet, ignSet);
		if (params.containsKey("id")) {
			return SetUtils.intersection(usefulSet, params.keySet()).toArray(ArrayUtils.toArray());
		} else {
			return usefulSet.toArray(ArrayUtils.toArray());
		}
	}

	private void validateObject(Class<? extends BasePoEntity> clazz, String key, Object data, Set<Integer> cache) throws Exception {
		int hash = System.identityHashCode(data);
		if (cache.contains(hash)) {
			return;
		} else {
			cache.add(hash);
		}
		@SuppressWarnings("unchecked")
		Class<? extends BasePoEntity> realClass = key == null ? clazz : (Class<? extends BasePoEntity>) PoEntityHelper.findTypeByProperty(clazz, key);
		@SuppressWarnings("unchecked")
		String[] fieldNames = getFieldNamesForValidate(realClass, Map.class.cast(data));
		for (String fieldName : fieldNames) {
			String[] valis = PoEntityHelper.getPropertiesData().descriptionMap.get(realClass.getSimpleName()).get(fieldName).getMode().getValidations();
			Object value = PoEntityHelper.readValueByProperty(data, fieldName);
			for (String vali : valis) {
				String[] valiTypeArg = findValiTypeArg(vali);
				PoVali.findValidator(valiTypeArg[0]).validate(realClass, fieldName, valiTypeArg[1], data);
			}
			if (value != null) {
				PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(realClass, fieldName);
				if (pd != null) {
					Class<?> directType = pd.getReadMethod().getReturnType();
					if (Collection.class.isAssignableFrom(directType)) {
						if (Collection.class.isInstance(value) || value.getClass().isArray()) {
							if (PoEntityHelper.isCascadable(realClass, fieldName)) {
								validateArray(realClass, fieldName, value, cache);
							} else {
								checkObjId(realClass, fieldName, value);
							}
						} else {
							throw new Exception("Field [" + clazz.getSimpleName() + "." + key + "." + fieldName + "] is not assign type [" + value.getClass().getSimpleName() + "]");
						}
					} else if (BasePoEntity.class.isAssignableFrom(directType)) {
						if (!PoEntityHelper.isBasicType(value)) {
							if (PoEntityHelper.isCascadable(realClass, fieldName)) {
								validateObject(realClass, fieldName, value, cache);
							} else {
								checkObjId(realClass, fieldName, value);
							}
						} else {
							throw new Exception("Field [" + clazz.getSimpleName() + "." + key + "." + fieldName + "] is not assign type [" + value.getClass().getSimpleName() + "]");
						}
					}
				}
			}
		}
	}

	private void checkObjId(Class<? extends BasePoEntity> realClass, String fieldName, Object value) {
		// PASS FOR NOW
	}

	private void validateArray(Class<? extends BasePoEntity> clazz, String key, Object value, Set<Integer> diyMap) throws Exception {
		for (Object obj : PoEntityHelper.toObjectArray(value)) {
			validateObject(clazz, key, obj, diyMap);
		}
	}

	private String[] findValiTypeArg(String vali) {
		int index = vali.indexOf("(");
		if (index > 0) {
			return ArrayUtils.toArray(vali.substring(0, index), vali.substring(index + 1, vali.length() - 1));
		} else {
			return ArrayUtils.toArray(vali, "");
		}
	}
}
